﻿using System;
using System.Linq;
using BrandonHaynes.Providers.AmazonS3.Permissions;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Users;
using DotNetNuke.Security.Permissions;
using DotNetNuke.Services.FileSystem;
using LitS3;

namespace BrandonHaynes.Providers.AmazonS3
	{
	/// <summary>
	/// A class providing helper extension methods to (hopefully!) increase code readability.
	/// </summary>
	static class AmazonS3Extensions
		{
		// The provider configuration may override the user property for key/secret storage.  These are
		// the attributes that are used to do this:
		//	<provider name="..." type="..." keyProfileProperty="..." secretProfileProperty="..." />
		private const string s3KeyProperty = "keyProfileProperty";
		private const string s3SecretProperty = "secretProfileProperty";

		// If no properties are explicitly specified in the provider configuration, we use some default ones.
		private const string defaultS3KeyProperty = "S3Key";
		private const string defaultS3SecretProperty = "S3Secret";

		/// <summary>
		/// The profile property name to use when storing a S3 key
		/// </summary>
		public static string S3KeyProfilePropertyName
			{ get { return GetProviderProperty(s3KeyProperty) ?? defaultS3KeyProperty; } }
		/// <summary>
		/// The profile property name to use when storing a S3 secret
		/// </summary>
		public static string S3SecretProfilePropertyName
			{ get { return GetProviderProperty(s3SecretProperty) ?? defaultS3SecretProperty; } }

		/// <summary>
		/// Helper method to load a provider setting from the provider configuration
		/// </summary>
		/// <param name="propertyName">The name of the provider setting to retrieve</param>
		private static string GetProviderProperty(string propertyName)
			{ return ((AmazonS3AuthorizationProvider)PermissionProvider.Instance()).Settings[propertyName]; }

		/// <summary>
		/// Determines (kind of) if a user has configured a S3 service for their account.  This method only checks
		/// for non-null values for the relevant profile values; it does not ensure that those values are valid.
		/// </summary>
		public static bool HasS3Service(this UserInfo user)
			{
			return !string.IsNullOrEmpty(user.Profile.GetPropertyValue(S3KeyProfilePropertyName)) &&
				   !string.IsNullOrEmpty(user.Profile.GetPropertyValue(S3SecretProfilePropertyName));
			}

		/// <summary>
		/// Determines (kind of) if a user has configured a S3 service for their account.  This method only checks
		/// for non-null values for the relevant profile values; it does not ensure that those values are valid.
		/// </summary>
		public static bool HasS3Service(this PortalSettings portalSettings)
			{ return UserController.GetUserById(portalSettings.PortalId, portalSettings.AdministratorId).HasS3Service(); }

		/// <summary>
		/// Gets the S3 service for the current portal (using the key/secret pair from the portal administrator)
		/// </summary>
		/// <param name="portalSettings">The portal settings to use in generating the S3 service</param>
		/// <returns>A S3 service for the given portal using the key/secret pair of the portal administrator</returns>
		public static S3Service S3Service(this PortalSettings portalSettings)
			{ return UserController.GetUserById(portalSettings.PortalId, portalSettings.AdministratorId).S3Service(); }

		/// <summary>
		/// Retrieves a S3Service for a given user, configured with that user's key/secret pair (loaded from profile)
		/// </summary>
		/// <param name="user">The user for which to generate a S3 service</param>
		/// <returns>A S3 service configured with the user's profile key/secret pair</returns>
		public static S3Service S3Service(this UserInfo user)
			{
			return new S3Service()
				{
				AccessKeyID = user.Profile.GetPropertyValue(S3KeyProfilePropertyName),
				SecretAccessKey = user.Profile.GetPropertyValue(S3SecretProfilePropertyName),
				UseSsl = false
				};
			}

		/// <summary>
		/// Given an S3 object entry, generates a DotNetNuke FileInfo fileId for that object.  
		/// Increases readability.
		/// </summary>
		public static int FileId(this ObjectEntry objectEntry)
			{ return S3File.MakeId(objectEntry.ETag); }

		/// <summary>
		/// Given a S3 object, converts to a DotNetNuke-IDataReadable-compatible S3File object
		/// </summary>
		/// <param name="objectEntry">The S3 object to convert</param>
		/// <param name="portalId">The current portalId</param>
		/// <param name="folder">A S3Folder to use in instantiating the S3File</param>
		/// <returns>A S3File representing the given S3 object</returns>
		public static S3File ToS3File(this ObjectEntry objectEntry, int portalId, S3Folder folder)
			{
			return new S3File()
				{
				FileID = objectEntry.FileId(),
				PortalID = portalId,
				FileName = objectEntry.Name,
				FolderID = folder.FolderID,
				Folder = folder.FolderPath,
				Size = objectEntry.Size
				};
			}

		/// <summary>
		/// Given a S3 object, converts to a DotNetNuke-IDataReadable-compatable S3File object
		/// </summary>
		/// <param name="objectEntry">The S3 object to convert</param>
		/// <param name="portalId">The current portalId</param>
		/// <param name="bucketName">The name of the S3 bucket name that contains the object entry</param>
		/// <returns>A S3File representing the given S3 object</returns>
		public static S3File ToS3File(this ObjectEntry objectEntry, int portalId, string bucketName)
			{
			return new S3File()
			{
				FileID = objectEntry.FileId(),
				PortalID = portalId,
				FileName = objectEntry.Name,
				FolderID = S3Folder.MakeId(bucketName),
				Folder = S3Folder.MakePath(bucketName),
				Size = objectEntry.Size
			};
			}

		/// <summary>
		/// Given a S3 bucket, converts to a DotNetNuke and IDataReadable-compatable S3Folder object
		/// </summary>
		public static S3Folder ToS3Folder(this Bucket bucket, int portalId)
			{ return new S3Folder(portalId, bucket.Name); }

		/// <summary>
		/// Given a S3 bucket identifier, returns the matching bucket returned from the S3 service
		/// </summary>
		/// <param name="service">The S3 service to use in retrieving buckets</param>
		/// <param name="id">The identifier to search for in the returned S3 buckets</param>
		/// <returns>The bucket matching the given id; null otherwise</returns>
		public static Bucket GetBucketById(this S3Service service, Int32 id)
			{
			return service.GetAllBuckets()
				.FirstOrDefault(bucket => id == S3Folder.MakeId(bucket.Name));
			}

		/// <summary>
		/// Locates the first bucket returned by the S3 service which matches the given path.
		/// This is an O(n) operation and could use some dictionary and cache love.
		/// </summary>
		/// <param name="service">The S3 service to use</param>
		/// <param name="path">The path to look for in the set of returned S3 buckets</param>
		/// <returns>The bucket matching the path; null if none found</returns>
		public static Bucket GetBucketByPath(this S3Service service, string path)
			{
			return service.GetAllBuckets()
					.FirstOrDefault(bucket => path == S3Folder.MakePath(bucket.Name));
			}

		/// <summary>
		/// Given a DotNetNuke folder, converts it to a bucket name (or null if the folder is not a S3 bucket)
		/// </summary>
		/// <param name="folder">A DotNetNuke folder object</param>
		/// <returns>The bucket name if the folder represents a S3 bucket; null otherwise</returns>
		public static string ToBucketName(this FolderInfo folder)
			{ return S3Folder.MakeBucketName(folder.FolderPath); }

		/// <summary>
		/// Helper method to download an array of bytes for an object hosted on the S3 service.
		/// Note that because the size of the hosted file may be very large, this method may consume
		/// a high amount of memory (and require a large amount of time to complete the transfer).
		/// Care should be used here!
		/// </summary>
		/// <param name="service">The service from which to download the object</param>
		/// <param name="bucketName">The name of the bucket where the object is stored</param>
		/// <param name="key">The key identifying the object to be downloaded</param>
		/// <returns>A byte array of the object contents</returns>
		public static byte[] GetObjectBytes(this S3Service service, string bucketName, string key)
			{
			long contentLength;
			string contentType;

			using (var stream = service.GetObjectStream(bucketName, key, out contentLength, out contentType))
				{
				var buffer = new byte[contentLength]; // Potentially on the order of 2gb -- yikes!
				stream.Read(buffer, 0, (int)contentLength);
				return buffer;
				}
			}
		}
	}
